#include "json.hpp"
#include "logger.hpp"
#include "yaml/include/yaml-cpp/yaml.h"
#include <mqttClient.hpp>
#include <threadPool.hpp>
#include <usb.hpp>

class InterfaceManager : public Logger {
private:
    using json = nlohmann::json;

    const std::string autoAddTopic = "dev/hand_dev/dev_modbus/autoadd"; /* 自发现 */
    const std::string addTopic = "dev/hand_dev/dev_modbus/add";         /* 添加设备 */
    const std::string devReadTopic = "dev/+/dev_modbus/read";           /* 读点 */
    const std::string devWriteTopic = "dev/+/dev_modbus/write";         /* 写点 */
    const std::string appReadTopic = "app/sys_manager/dev_modbus/read"; /* app读 */
    const std::string addBroadcastTopic = "dev/dev_modbus/0/added";     /* 添加广播 */
    const std::string infoTopic = "dev/dev_modbus/0/info";              /* 0 info */
    const std::size_t poolSize = 4;                                     /* 线程池容量 */
    const std::string selfSign = "dev_modbus";                          /* 自身的src标识 */
    const std::unordered_multimap<std::string, std::pair<uint16_t, uint16_t>> modelVidPid = {
        /* 型号的pid与vid对照表 */
        {"NA1-M", {0x1A86, 0xE010}},
        {"NA1-M", {0x1A86, 0xe429}},
        {"NA1-3M", {0x1A86, 0xE010}},
        {"NA1-3M", {0x1A86, 0xe429}},
        {"NA1-3H", {0x1A86, 0xE010}},
        {"NA1-3H", {0x1A86, 0xe429}},
        {"NA1-H", {0x1A86, 0xE010}},
        {"NA1-H", {0x1A86, 0xe429}},
        {"NXA-P", {0x1A86, 0xE010}},
        {"NXA-P", {0x1A86, 0xe429}},
        {"NXA-H", {0x1A86, 0xE010}},
        {"NXA-H", {0x1A86, 0xe429}},
        {"NA8-M", {0x1A86, 0xE010}},
        {"NA8-M", {0x1A86, 0xe429}},
        {"NA8-H", {0x1A86, 0xE010}},
        {"NA8-H", {0x1A86, 0xe429}},
    };

private:
    MqttClient *_mqtt;
    UsbInterface *_usb;
    ThreadPool *_pool;
    // TODO: MODBUS HANDLER

private:
    bool parseMqttMsg(const MqttClient::Msg_t &msg);
    bool appReadResponse(const MqttClient::Msg_t &msg);
    bool manualAddMsgParse(const MqttClient::Msg_t &msg);
    bool manualAddMsgResponse(const MqttClient::Msg_t &msg, bool optResult = true);
    bool addBroadcast(const std::string &name, const std::string &profile, const std::string &type, int baudrate, int slaveAddr, int byteSize, int stopBits, const std::string &parity, const std::string &port);

    bool devReadMsgParse(const MqttClient::Msg_t &msg);
    bool devReadResponse(int tick, const std::vector<std::string> &keys, const std::string &topic);

    bool devWriteMsgParse(const MqttClient::Msg_t &msg);
    bool devWriteResponse(int tick, const std::vector<std::string> &keys, const std::string &topic);

    bool onUsbExit(void);

public:
    std::string currentTime(void);
    std::vector<std::string> split(const std::string &str, const std::string &pattern);

public:
    InterfaceManager();
    ~InterfaceManager();

    void waitMqttMsgThread(void);
};

InterfaceManager::InterfaceManager()
    : _mqtt(new MqttClient("dev_modbus"))
    , _usb(new UsbInterface())
    , _pool(new ThreadPool(poolSize))

{
    information("dev_modbus version : {}", DEV_MODBUS_VERSION);

    _mqtt->connect("localhost");

    _mqtt->registerOnConnected(std::bind(&MqttClient::subscribe, _mqtt, autoAddTopic));
    _mqtt->registerOnConnected(std::bind(&MqttClient::subscribe, _mqtt, addTopic));
    _mqtt->registerOnConnected(std::bind(&MqttClient::subscribe, _mqtt, devReadTopic));
    _mqtt->registerOnConnected(std::bind(&MqttClient::subscribe, _mqtt, devWriteTopic));
    _mqtt->registerOnConnected(std::bind(&MqttClient::subscribe, _mqtt, appReadTopic));

    _usb->registerOnUsbExit(std::bind(&InterfaceManager::onUsbExit, this));

    std::thread(&InterfaceManager::waitMqttMsgThread, this).detach();
}

bool InterfaceManager::onUsbExit(void)
{
    if (!_mqtt->isConnected()) {
        return false;
    }

    json root;
    root["tick"] = 1;
    root["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["src"] = selfSign;
    root["dev"]["status"] = "offline";

    return _mqtt->publish(infoTopic, root.dump());
}

void InterfaceManager::waitMqttMsgThread(void)
{
    while (true) {
        auto msg = _mqtt->getOneItem();
        // _pool->exec(&InterfaceManager::parseMqttMsg, this, msg).get();
        parseMqttMsg(msg);
    }
}

bool InterfaceManager::parseMqttMsg(const MqttClient::Msg_t &msg)
{
    if (msg.topic.find("app/") != std::string::npos && msg.topic.find("/read") != std::string::npos) {
        return appReadResponse(msg);
    } else if (msg.topic.find("dev/") != std::string::npos && msg.topic.find("/add") != std::string::npos) {
        return manualAddMsgParse(msg);
    } else if (msg.topic.find("dev/") != std::string::npos && msg.topic.find("/read") != std::string::npos) {
        return devReadMsgParse(msg);
    } else if (msg.topic.find("dev/") != std::string::npos && msg.topic.find("/write") != std::string::npos) {
        return devWriteMsgParse(msg);
    }

    return true;
}

std::string InterfaceManager::currentTime(void)
{
    auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    std::ostringstream oss;
    oss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S");
    return oss.str();
}

std::vector<std::string> InterfaceManager::split(const std::string &str, const std::string &pattern)
{
    std::vector<std::string> result;
    std::string::size_type pos = 0;
    std::string::size_type prev = 0;

    while ((pos = str.find(pattern, prev)) != std::string::npos) {
        auto r = str.substr(prev, pos - prev);

        r.size() ? result.push_back(r) : (void)(nullptr);

        prev = pos + pattern.length();
    }
    auto r = str.substr(prev);
    r.size() ? result.push_back(r) : (void)(nullptr);

    return result;
}

bool InterfaceManager::appReadResponse(const MqttClient::Msg_t &msg)
{
    json root;
    root["tick"] = 1;
    root["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["src"] = selfSign;
    root["ack"]["tick"] = 1;
    root["ack"]["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["ack"]["status"] = "success";

    json obj;
    obj["key"] = "log";
    obj["val"] = "dev_modbus log";
    root["app"]["points"].push_back(obj);

    obj["key"] = "version";
    obj["val"] = DEV_MODBUS_VERSION;
    root["app"]["points"].push_back(obj);

    obj["key"] = "date";
    obj["val"] = currentTime();
    root["app"]["points"].push_back(obj);

    auto result = split(msg.topic, "/");

    return _mqtt->publish(result[0] + "/" + result[2] + "/" + result[1] + "/rack", root.dump());
}

bool InterfaceManager::manualAddMsgResponse(const MqttClient::Msg_t &msg, bool optResult)
{
    json root = json::parse(msg.payload);

    root["src"] = selfSign;
    root["ack"]["tick"] = 1;
    root["ack"]["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();

    if (optResult) {
        root["ack"]["status"] = "success";
    } else {
        root["ack"]["status"] = "failed";
    }

    if (optResult) {
        root["status"] = "success";
    } else {
        root["status"] = "failed";
    }

    auto result = split(msg.topic, "/");

    return _mqtt->publish(result[0] + "/" + result[2] + "/" + result[1] + "/addack", root.dump());
}

bool InterfaceManager::manualAddMsgParse(const MqttClient::Msg_t &msg)
{
    json root = json::parse(msg.payload);

    if (root.empty()) {
        return manualAddMsgResponse(msg, false);
    }

    if (!root.contains("dev")) {
        return manualAddMsgResponse(msg, false);
    }

    if (!root["dev"].contains("name")) {
        return manualAddMsgResponse(msg, false);
    }
    const std::string name = root["dev"]["name"];

    if (!root["dev"].contains("type")) {
        return manualAddMsgResponse(msg, false);
    }
    const std::string type = root["dev"]["type"];

    if (!root["dev"].contains("profile")) {
        return manualAddMsgResponse(msg, false);
    }
    const std::string profile = root["dev"]["profile"];

    if (!root["dev"].contains("param")) {
        return manualAddMsgResponse(msg, false);
    }

    int address = root["dev"]["param"]["address"];
    int baudrate = root["dev"]["param"]["baudrate"];
    int byteSize = root["dev"]["param"]["bytesize"];
    int stopBits = root["dev"]["param"]["stopbits"];
    const std::string parity = root["dev"]["param"]["parity"];
    const std::string port = root["dev"]["param"]["port"];

    if (!root.contains("status")) {
        // TODO: status check
    }

    if (type.find("usb") != std::string::npos) {
        _usb->setBaudrate(baudrate).setDataBits(byteSize).setStopBits(stopBits).setParity(parity).setSlaveAddr(address);
        if (!_usb->initPointTable(profile)) {
            error("init point table failed");
            return manualAddMsgResponse(msg, false);
        }
    } else {
        // TODO:485设备
    }

    // if (modelVidPid.find(name) == modelVidPid.end()) {
    //     return manualAddMsgResponse(msg, false);
    // }
    _usb->closeUsbDevice();

    auto range = modelVidPid.equal_range(name);
    for (auto it = range.first; it != range.second; it++) {
        if (_usb->findUsbDevice(it->second.first, it->second.second)) {
            break;
        }
    }

    if (!_usb->isFindDev()) {
        spdlog::info("can't find usb device");
        return manualAddMsgResponse(msg, false);
    }

    if (!_usb->openUsbDevice()) {
        spdlog::info("can't open usb device");
        return manualAddMsgResponse(msg, false);
    }

    auto point = _usb->getFirstPoint();
    if (!point) {
        spdlog::info("can't find readable point\n");
        _usb->closeUsbDevice();
        return manualAddMsgResponse(msg, false);
    }

    if (!_usb->readPoint(point->first)) {
        spdlog::info("point read failed\n");
        _usb->closeUsbDevice();
        return manualAddMsgResponse(msg, false);
    }

    if (!point->second.operationResult()) {
        spdlog::info("point read failed\n");
        _usb->closeUsbDevice();
        return manualAddMsgResponse(msg, false);
    }

    addBroadcast(name, profile, type, baudrate, address, byteSize, stopBits, parity, port);
    return manualAddMsgResponse(msg);
}

bool InterfaceManager::addBroadcast(const std::string &name,
                                    const std::string &profile,
                                    const std::string &type,
                                    int baudrate,
                                    int slaveAddr,
                                    int byteSize,
                                    int stopBits,
                                    const std::string &parity,
                                    const std::string &port)
{
    json root;
    root["tick"] = 1;
    root["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["src"] = selfSign;
    root["dev"]["name"] = name;
    root["dev"]["uuid"] = name;
    root["dev"]["type"] = type;
    root["dev"]["model"] = name;
    root["dev"]["profile"] = profile;
    root["dev"]["param"]["address"] = slaveAddr;
    root["dev"]["param"]["baudrate"] = baudrate;
    root["dev"]["param"]["bytesize"] = byteSize;
    root["dev"]["param"]["stopbits"] = stopBits;
    root["dev"]["param"]["parity"] = parity;
    root["dev"]["param"]["port"] = port;
    root["status"] = "success";

    return _mqtt->publish(addBroadcastTopic, root.dump());
}

bool InterfaceManager::devReadResponse(int tick, const std::vector<std::string> &keys, const std::string &topic)
{
    json root;
    root["tick"] = tick;
    root["time"] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["src"] = selfSign;
    root["ack"]["tick"] = tick;
    root["ack"]["time"] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["ack"]["status"] = "success";

    for (const auto &key : keys) {
        auto points = _usb->findPointByName(key);
        if (!points.size()) {
            json item;
            item["key"] = key;
            item["val"] = 0;
            item["status"] = "failed";
            item["errCode"] = "can't find point name";
            root["dev"]["points"].push_back(item);
            continue;
        }

        for (auto &item : points) {
            json point;
            point["key"] = item->first;
            if (item->second.type != "string" && item->second.type != "BCD") {
                point["val"] = item->second.rValue * item->second.unit;
            } else {
                point["val"] = item->second.rString;
            }
            if (item->second.operationResult()) {
                point["status"] = "success";
            } else {
                point["status"] = "failed";
                point["errCode"] = "can't recv some data";
            }
            root["dev"]["points"].push_back(point);
        }
    }

    auto result = split(topic, "/");

    return _mqtt->publish(result[0] + "/" + result[2] + "/" + result[1] + "/rack", root.dump());
}

bool InterfaceManager::devReadMsgParse(const MqttClient::Msg_t &msg)
{
    json root = json::parse(msg.payload);

    int tick = root["tick"];

    std::vector<std::string> keys;

    for (auto &item : root["dev"]["points"]) {
        const std::string pointName = item["key"];
        keys.push_back(pointName);
        _usb->readPoint(pointName);
    }

    return devReadResponse(tick, keys, msg.topic);
}

bool InterfaceManager::devWriteResponse(int tick, const std::vector<std::string> &keys, const std::string &topic)
{
    json root;
    root["tick"] = tick;
    root["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["src"] = selfSign;
    root["ack"]["tick"] = tick;
    root["ack"]["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["ack"]["status"] = "success";

    for (const auto &key : keys) {
        auto points = _usb->findPointByName(key);
        if (!points.size()) {
            json item;
            item["key"] = key;
            item["val"] = 0;
            item["status"] = "failed";
            item["errCode"] = "can't find point name";
            root["dev"]["points"].push_back(item);
            continue;
        }

        for (auto &item : points) {
            json point;
            point["key"] = item->first;
            point["val"] = item->second.wValue;

            if (item->second.operationResult()) {
                point["status"] = "success";
            } else {
                point["status"] = "failed";
                point["errCode"] = "time out";
            }
            root["dev"]["points"].push_back(point);
        }
    }

    auto result = split(topic, "/");
    return _mqtt->publish(result[0] + "/" + result[2] + "/" + result[1] + "/wack", root.dump());
}

bool InterfaceManager::devWriteMsgParse(const MqttClient::Msg_t &msg)
{
    json root = json::parse(msg.payload);

    int tick = root["tick"];

    std::vector<std::string> keys;

    for (auto &item : root["dev"]["points"]) {
        const std::string pointName = item["key"];
        const uint32_t val = item["val"];
        _usb->writePoint(pointName, val);
        keys.push_back(pointName);
    }

    return devWriteResponse(tick, keys, msg.topic);
}

InterfaceManager::~InterfaceManager()
{
    if (_mqtt) {
        delete _mqtt;
    }

    if (_usb) {
        delete _usb;
    }

    if (_pool) {
        delete _pool;
    }
}
